{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center>\n",
    "<img src=\"../../img/ods_stickers.jpg\">\n",
    "## Открытый курс по машинному обучению\n",
    "<center>Автор материала: Лозовой Виталий Витальевич, @vitaliylo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# <center>Тьюториал по Tensorflow Hub</center>\n",
    "## <center>Получаем сложные признаки простым способом</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "Так уж сложилось что искуственные нейронные сети сейчас в почете. Тема нейронных сетей довольно сложная и местами неоднозначная. Для большинства задач, когда есть четкие признаки - \"классические\" алгоритмы машинного обучения справляются лучше, но в задачах когда признаки сильно абстрактные(большинство задач работы с изображениями, и большая часть текстовых задач) - глубоким нейронным сетям нет равных. Но нейронная сеть - штука громоздкая, нужно много данных, часто для обучения требуются дни, а чтобы хоть как-то ускорить процес - может понадобится не один GPU. Поетому часто можно наити готовые, предобученые модели для некоторых задач. Сегодня я расскажу именно о таком случае.\n",
    "\n",
    "Мы посмотрим что из себя представляет Tensorflow Hub и как его можно использовать не для глубокого обучения. \n",
    "\n",
    "#### Начнем с начала:\n",
    "<a href=\"https://en.wikipedia.org/wiki/TensorFlow\">Tensorflow</a> - библиотека глубокого обучения от Google, отчасти написана <a href=\"https://en.wikipedia.org/wiki/Jeff_Dean_(computer_scientist)\">Ним Самым</a>. Ее в качестве бекенда может использовать популярный фреймворк Keras, и в ней есть как объекты высокого уровня (estimators, layers), так и возможность писать всю математику самому.\n",
    "К сожалению тема слишком обширная чтоб ее разворачивать в маленьком туториале, да и он не об этом.\n",
    "#### А о чем?\n",
    "Туториал будет об использовании <a href=\"https://www.tensorflow.org/hub/\">Tensorflow hub</a> - модуле с готовыми обучеными моделями и их частями. Это некий ящик с инструментами, который может помочь сэкономить время обучения нейронных сетей(transfer learning) или **сгенерировать признаки высокого уровня абстракции**.\n",
    "#### Как?\n",
    "Начнем с установки:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!pip install tensorflow\n",
    "#pip install tensorflow-gpu # для тех у кого установлена CUDA\n",
    "!pip install tensorflow-hub"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import re\n",
    "import warnings\n",
    "\n",
    "import matplotlib.image as mpimg\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import seaborn as sns\n",
    "import tensorflow as tf\n",
    "import tensorflow_hub as hub\n",
    "from PIL import Image, ImageOps\n",
    "from scipy.spatial import cKDTree, distance\n",
    "from skimage.feature import plot_matches\n",
    "from skimage.measure import ransac\n",
    "from skimage.transform import AffineTransform\n",
    "\n",
    "warnings.filterwarnings('ignore')\n",
    "\n",
    "%matplotlib inline\n",
    "# константа для задания размеров графиков\n",
    "FIGSIZE_TUPLE = (12, 9)\n",
    "\n",
    "# настройка внешнего вида графиков в seaborn\n",
    "sns.set_context(\n",
    "    \"notebook\", \n",
    "    font_scale = 1.5,      \n",
    "    rc = { \n",
    "        \"figure.figsize\" : FIGSIZE_TUPLE, \n",
    "        \"axes.titlesize\" : 18 \n",
    "    }\n",
    ")\n",
    "sns.set_style('darkgrid')\n",
    "sns.set(rc={'figure.figsize':FIGSIZE_TUPLE}, font_scale=1.5)\n",
    "from matplotlib import rcParams\n",
    "\n",
    "rcParams['figure.figsize'] = FIGSIZE_TUPLE"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим что он умеет. Вот список моделей в наличии:\n",
    "- delf - сеть анализа изображений, натренирована на фотографиях достопримечательностей\n",
    "- imagenet - целая пачка моделей разных типов и размеров, которые принимали участие в imagenet в разные годы\n",
    "- progan - генеративная сеть обученая на фото знаменитостей\n",
    "- elmo - text embedding\n",
    "- nnlm - text embedding с обработкой out of vocabulary. Есть версии для разных размеров выходных векторов и разных языков(русского нет).\n",
    "- universal sentance encoder - это уже что-то ближе к doc2vec. Модуль заточен на semantic similarity\n",
    "- wiki-words - снова word2vec, обученый на английской wiki\n",
    "\n",
    "Довольно много всякого.\n",
    "#### А как я могу это использовать?\n",
    "Можно получать из текстов - \"смысловой вектор\". Это простейший из способов получить doc2vec который мне известен"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Качаем модель. В первый раз занимает достаточно много времени.\n",
    "embed = hub.Module(\"https://tfhub.dev/google/universal-sentence-encoder/1\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Он любит спамить в лог. Если мы не любопытны к чужим логам - лучше отключить.\n",
    "tf.logging.set_verbosity(tf.logging.ERROR)\n",
    "\n",
    "with tf.Session() as session:\n",
    "  session.run([tf.global_variables_initializer(), tf.tables_initializer()])\n",
    "  embeddings = session.run(embed(['word']))\n",
    "\n",
    "  print(\"Слово: word\")\n",
    "  print(\"Размер смыслового вектора: {}\".format(len(embeddings[0])))\n",
    "  print(\"Часть вектора: {}, ...\\n\".format(embeddings[0][::10]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Модель называется universal **sentance** encoder, поетому давайте попробуем на предложениях"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sentences = [\n",
    "    # You know...\n",
    "    \"I really love to study data science, specially deep learning\",\n",
    "    \"Machine learning is cool. I like to do it\",\n",
    "    \"Neural networks are not so hard when you use tensorflow hub.\",\n",
    "\n",
    "    # Travelling\n",
    "    \"I'm a hitchhiker.\",\n",
    "    'Whay kind of travelling do you prefere?',\n",
    "    \"This year I've visited a few foreign countries\",\n",
    "    \"Have you ever been in a long trip?\",\n",
    "\n",
    "    # Weather?\n",
    "    \"Nice weather isn't it?\",\n",
    "    \"Looks like it's hot today\",\n",
    "    \"Do you believe in global warming?\",\n",
    "    \n",
    "    # Fastfood\n",
    "    'KFC',\n",
    "    'McDonalds',\n",
    "    'Dominos',\n",
    "    \n",
    "    # Graphomania\n",
    "    \"I can even write a very long text here. Somehow the long text can anyway have some general meaning. There's a lot of words\",\n",
    "    \"War and Peace\",\n",
    "    \"Leo Tolstoy\",\n",
    "  \n",
    "    \"Tesla\",\n",
    "    \"AC/DC\",\n",
    "    \"Rock\",\n",
    "    \"Car with autopilot\"\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Прогоним наши фразы через doc2vec, и попробуем найти степень схожести векторов друг с другом"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def run_and_plot(session_, input_tensor_, messages_, encoding_tensor):\n",
    "  message_embeddings_ = session_.run(\n",
    "      encoding_tensor, feed_dict={input_tensor_: messages_})\n",
    "  messages_ = list(map(lambda m: m[:25]+'...' if len(m)>30 else m, messages_))\n",
    "  g = sns.heatmap(\n",
    "      np.inner(message_embeddings_, message_embeddings_),\n",
    "      xticklabels=messages_,\n",
    "      yticklabels=messages_,\n",
    "      vmin=0,\n",
    "      vmax=1,\n",
    "      cmap=\"Blues\")\n",
    "  g.set_xticklabels(messages_, rotation=90)\n",
    "  g.set_title(\"Semantic Textual Similarity\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "similarity_input_placeholder = tf.placeholder(tf.string, shape=(None))\n",
    "similarity_encodings = embed(similarity_input_placeholder)\n",
    "with tf.Session() as session:\n",
    "  session.run(tf.global_variables_initializer())\n",
    "  session.run(tf.tables_initializer())\n",
    "  run_and_plot(session, similarity_input_placeholder, sentences,\n",
    "               similarity_encodings)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Как видим - даже из многословных текстов можна выжать некий смысл который будет иметь видимую корреляцию со сходными понятиями или понятиями из схожего контекста. К сожалению чем больше текста в каждом примере, тем менее строго будут коррелировать понятия, так как большой текст имеет гораздо более разнообразный смысл, чем одно слово.\n",
    "#### Что там еще?\n",
    "Таким же образом мы можем поступить и с картинками - используя `hub.Module(\"https://tfhub.dev/google/imagenet/inception_v3/feature_vector/1\")` из пакета imagenet - он даст вектор из 2048 признаков, которые нейросеть нашла на картинке.\n",
    "Интересная модель delf - она позволяет найти \"важные\" точки на картинке и соответствующие им векторы признаков. Таким образом можно сравнивать картинки"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tf.reset_default_graph()\n",
    "tf.logging.set_verbosity(tf.logging.INFO)\n",
    "\n",
    "m = hub.Module('https://tfhub.dev/google/delf/1') # Это будет долго"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "IMAGE_1 = \"../../img/image_1.jpg\"\n",
    "IMAGE_2 = \"../../img/image_2.jpg\"\n",
    "\n",
    "def show_images(image_path_list):\n",
    "  plt.figure()\n",
    "  for i, image_path in enumerate(image_path_list):\n",
    "    plt.subplot(1, len(image_path_list), i+1)\n",
    "    plt.imshow(np.asarray(Image.open(image_path)))\n",
    "    plt.title(image_path)\n",
    "    plt.grid(False)\n",
    "    plt.yticks([])\n",
    "    plt.xticks([])\n",
    "  plt.show()\n",
    "\n",
    "show_images([IMAGE_1, IMAGE_2])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def image_input_fn():\n",
    "  filename_queue = tf.train.string_input_producer(\n",
    "      [IMAGE_1, IMAGE_2], shuffle=False)\n",
    "  reader = tf.WholeFileReader()\n",
    "  _, value = reader.read(filename_queue)\n",
    "  image_tf = tf.image.decode_jpeg(value, channels=3)\n",
    "  return tf.image.convert_image_dtype(image_tf, tf.float32)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "image_placeholder = tf.placeholder(\n",
    "    tf.float32, shape=(None, None, 3), name='input_image')\n",
    "\n",
    "module_inputs = {\n",
    "    'image': image_placeholder,\n",
    "    'score_threshold': 100.0,\n",
    "    'image_scales': [0.25, 0.3536, 0.5, 0.7071, 1.0, 1.4142, 2.0],\n",
    "    'max_feature_num': 1000,\n",
    "}\n",
    "\n",
    "module_outputs = m(module_inputs, as_dict=True)\n",
    "\n",
    "image_tf = image_input_fn()\n",
    "\n",
    "with tf.train.MonitoredSession() as sess:\n",
    "  results_dict = {}  # Stores the locations and their descriptors for each image\n",
    "  for image_path in [IMAGE_1, IMAGE_2]:\n",
    "    image = sess.run(image_tf)\n",
    "    print('Extracting locations and descriptors from %s' % image_path)\n",
    "    results_dict[image_path] = sess.run(\n",
    "        [module_outputs['locations'], module_outputs['descriptors']],\n",
    "        feed_dict={image_placeholder: image})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "distance_threshold = 0.8\n",
    "\n",
    "locations_1, descriptors_1 = results_dict[IMAGE_1]\n",
    "num_features_1 = locations_1.shape[0]\n",
    "print(\"На картинке image 1 нашлось %d признаков\" % num_features_1)\n",
    "locations_2, descriptors_2 = results_dict[IMAGE_2]\n",
    "num_features_2 = locations_2.shape[0]\n",
    "print(\"На картинке image 2 нашлось %d признаков\" % num_features_2)\n",
    "\n",
    "# среди признаков поиещем похожие с помощью KD tree.\n",
    "d1_tree = cKDTree(descriptors_1)\n",
    "_, indices = d1_tree.query(\n",
    "    descriptors_2, distance_upper_bound=distance_threshold)\n",
    "\n",
    "# Select feature locations for putative matches.\n",
    "locations_2_to_use = np.array([\n",
    "    locations_2[i,]\n",
    "    for i in range(num_features_2)\n",
    "    if indices[i] != num_features_1\n",
    "])\n",
    "locations_1_to_use = np.array([\n",
    "    locations_1[indices[i],]\n",
    "    for i in range(num_features_2)\n",
    "    if indices[i] != num_features_1\n",
    "])\n",
    "\n",
    "# Разделяем на выбросы и обычные точки с помощью RANdom SAmple Consensus.\n",
    "_, inliers = ransac(\n",
    "    (locations_1_to_use, locations_2_to_use),\n",
    "    AffineTransform,\n",
    "    min_samples=3,\n",
    "    residual_threshold=20,\n",
    "    max_trials=1000)\n",
    "\n",
    "print('Найдено %d точек' % sum(inliers))\n",
    "\n",
    "# Отрисуем связи которые модель нашла.\n",
    "_, ax = plt.subplots()\n",
    "inlier_idxs = np.nonzero(inliers)[0]\n",
    "plot_matches(\n",
    "    ax,\n",
    "    mpimg.imread(IMAGE_1),\n",
    "    mpimg.imread(IMAGE_2),\n",
    "    locations_1_to_use,\n",
    "    locations_2_to_use,\n",
    "    np.column_stack((inlier_idxs, inlier_idxs)),\n",
    "    matches_color='b')\n",
    "ax.axis('off')\n",
    "ax.set_title('Соответствия DELF');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Заключение\n",
    "Иногда хочется получить преимущества глубокого обучения без недостатков этого самого глубокого обучения. Я считаю что tensorflow hub - полезный инструмент в арсенале, особенно в случаях когда:\n",
    "- Нет времени/желания/навыка/вычислительных ресурсов обучить полноценную глубокую нейронную сеть\n",
    "- Нужна предобученая модел для теплого старта\n",
    "- Нужно получить фичи нейронной сети, а не использовать ее как полноценную модель\n",
    "- Нужна полноценная модель, но быстро и дешево\n",
    "\n",
    "Рекомендую всем скачать и поиграться, это как минимум интересно.\n",
    "\n",
    "_П.С. Да, он криво заматчил ёлочки с крышами_ ¯\\&#95;(ツ)&#95;/¯"
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
